
#ifndef __RSP_S_NET__HEADER_FILE__
#define __RSP_S_NET__HEADER_FILE__


#include "coid/comm/binstream/netstreamudp.h"
#include "coid/comm/hptimer.h"

#include "s_connector.h"
#include "s_world.h"


////////////////////////////////////////////////////////////////////////////////
///Server side networking controller
/**
    This base class handles external clients connected to the managed world.

**/
class rsp_s_net
{
public:

    rsp_s_net() : _net(32769)
    {
        MsecTimer::init();
        init_server_log();

        _frame_usec = 10000;//20000;//16667;
        _Timer.set_period_usec(_frame_usec);

        RSPS_SETTINGS.set_params( 96, 4 );
        //_net.set_packet_size( RSPS_SETTINGS._maxpacketsize );

        _prev_stateid = 0;

        _bnewframe = true;
        _nframesync = 0;
        _attbuf.reserve(4,false);
    }

    virtual ~rsp_s_net()
    {
        MsecTimer::term();
    }

    ///Create rsp_s_room_controller object
    virtual rsp_world_controller* create_world() = 0;



    void server_process( void (*callback)(void) )
    {
        netAddress a;
        uint tp = (RSPS_SETTINGS._maxpacketperframe * 8 * 1000000) / 1024 / _Timer.get_period_usec();

        LOG(-1) {
            *TLOG << "starting server on " << _net.get_local_address(&a) << "\n";
            *TLOG << "frame duration: " << (float(_frame_usec)/1000) << "ms" << "\n";
            *TLOG << "waiting queue: " << RSP_SETTINGS._nleading <<
                " (" << (float(RSPS_SETTINGS._nleading*_frame_usec)/1000) << "ms)\n";
            *TLOG << "trailing queue: " << RSPS_SETTINGS._ntrailing <<
                " (" << (float(RSPS_SETTINGS._ntrailing*_frame_usec)/1000) << "ms)\n";
            *TLOG << "max.packet size: " << RSPS_SETTINGS._maxpacketsize << " bytes\n";

            *TLOG << "max.throughput per client: " << tp << " kbps\n";
            *TLOG << BINSTREAM_FLUSH;
        }

        _world->initialize_tiles();
        //init_rooms(1);

        _prev_stateid = 0;
        sync_timer();

        uint tck = 0;
        while(1)
        {
            receive_data(tck+1);

            int time = _Timer.usec_to_tick( tck+1 );
            if(time>0)
            {
                if(_nframesync) {
                    if(callback) callback();
                    continue;   //no sleep when a sync request expected
                }

                if(callback) {
                    callback();
                    time = _Timer.usec_to_tick( tck+1 );
                }

                if( time > 1000 )
                {
#ifdef SYSTYPE_LINUX
                    sysMilliSecondSleep(0);     //linux 2.4 sleeps to next slice, 10ms

                    //uint t2 = _Timer.usec_to_tick( tck+1 );
                    //if( time-t2 > 10000 )
                    //    *LOG << "10ms sleep lasted " << (time-t2)/1000 << "ms\n";
#else
                    sysMilliSecondSleep(1);
#endif
                    //uint t2 = _Timer.usec_to_tick( tck+1 );

                    //if( time-t2 > 2000 )
                    //    *LOG << "1ms sleep lasted " << (time-t2)/1000 << "ms\n";
                }
#ifdef SYSTYPE_LINUX
#else
                else
                    sysMilliSecondSleep(0);
#endif
            }
            else
            {
                //if( (tck+1)%50 == 0 )
                //    *LOG << timefmt() << BINSTREAM_FLUSH;

                tck = process_frame();
            }
        }
    }


private:

    void receive_data( uint tck )
    {
        //receive messages from client
        // TODO: add checking for remaining time
        while( _net.data_available(0) )
        {
            int time = _Timer.usec_to_tick( tck );
            if( time <= -1000 )
                return;     //stop receiving if it takes too long and a transition is underway

            //queue commands from clients
            //instant reply to time sync messages
            int st = receive_packet();
            if(st>=0)
                _net.acknowledge(st==0);
        }

        _bnewframe = false;
    }

    ///Process input data, physics and send messages
    uint process_frame()
    {
        //get actual frame number
        uint tck = _Timer.ticks();

        //test if we should output next turn
        // should not ever be <0
        if( tck - _prev_stateid == 0 )
            return tck;

        _bnewframe = true;

        if( int(tck - _prev_stateid) > 2 )
            LOG(1) { *TLOG << "lost time: prev=" << _prev_stateid << " step: " << (tck - _prev_stateid) << " frames" << BINSTREAM_FLUSH; }

        //here probably (afr - _prev_stateid) should not be
        // too big or else we slept way too much and
        // world is out of sync
        //if( tck - _prev_stateid > 16 )
        //    RSP_SETTINGS.active_frame() = _prev_stateid = tck - 1;    //TODO: check if clients can cope with this

        //do physics
        for( ; tck - _prev_stateid > 0; )
        {
            _world->process_frame( RSP_SETTINGS.active_frame() );

            if(_nframesync)
                --_nframesync;

            RSP_SETTINGS.advance_frame();
        }

        //do network
        _world->process_diffs();


        process_attach_requests();

        return process_frame();
    }

    ///Process the attach requests
    void process_attach_requests()
    {
        for( uint a=0; a<_attbuf.size(); ++a )
        {
            const AttachReq& atr = _attbuf[a];
            rsp_controller::current = _world.ptr();

            _net.set_remote_address( atr.addr );
            _net << (uint8)RSP_PACKET::CONNECT << atr.rqid;

            uint sn;
            rsp_s_connector* con;

            opcd e = _world->attach_connector( _net, atr, &con, &sn );
            _net << e;

            if(!e)
            {
                _net << con->get_ssid() << (ushort)sn << (uint8)RSP_SETTINGS._nleading << (uint8)RSP_SETTINGS._ntrailing;

                LOG(0) {
                    *TLOG << "ATTACHING \"" << con->get_nick() << "\" from " << _net.get_remote_address()
                    << " slot: " << sn << ", ssid: " << con->get_ssid() << " at " << timefmt() << BINSTREAM_FLUSH;
                }
            }
            else
            {
                LOG(0) {
                    *TLOG << "REJECTED attach request from " << _net.get_remote_address()
                    << " error: " << opcd_formatter(e) << " at " << timefmt() << BINSTREAM_FLUSH;
                }
            }

            _net.flush();
        }

        _attbuf.reset();
    }

/*
    void init_rooms( uint n )
    {
        _rooms.reserve( n, false );
        for( uint i=0; i<n; ++i )
        {
            //if( _rooms[i].is_set() )
            //    continue;

            rsp_s_room_controller* room=0;
            opcd e = create_room(&room);
            if(!e)
            {
                *_rooms.add() = room;
                room->attach_room();
            }
        }
    }
*/

    ///Receive one client message
    /// 2B slot, 4B ssid, controller
    /// @return >0 if message was received successfully, 0 if not and <0 if time check needed
    int receive_packet()
    {
        //a ping/sync message
        uint8 ty;
        _net >> ty;

        //request id
        uint32 id;
        _net >> id;

        if( ty == RSP_PACKET::SYNC )           //sync message
        {
            ushort ver_rsp;
            ushort ver_room;

            _net >> ver_rsp >> ver_room;
            bool fail = !_nframesync || _bnewframe;

            if( ver_rsp != Grsp_version  ||  ver_room != _world->get_version() )
            {
                _net << (uint8)RSP_PACKET::SYNC << id << (uint8)RSP_PACKET::SYNC_VERSION_MISMATCH
                    << Grsp_version << _world->get_version();
            }
            else if(fail)
            {
                _net << (uint8)RSP_PACKET::SYNC << id << (uint8)RSP_PACKET::SYNC_RETRY << _frame_usec; //can't sync
                _nframesync = N_FRAMES_SYNC;
            }
            else
            {
                _net << (uint8)RSP_PACKET::SYNC << id << (uint8)RSP_PACKET::SYNC_OK << _frame_usec;    //sync
                _net << _Timer.time();
            }
            _net.flush();

            LOG(1) {
                *TLOG << "SYNC REQUEST: " << (fail ? "retry" : "ok") << " at " << timefmt() << BINSTREAM_FLUSH;
            }

            return 1;
        }
        else if( ty == RSP_PACKET::CONNECT )      //attach request
        {
            uint stime = _Timer.time();

            //attach requests will be processed in next frame, just queue here
            ushort mseclag;
            uint atime;
            _net >> mseclag >> atime;

            uint rqlen;
            const uchar* rqdata;
            _net.get_raw_unread_data( rqdata, rqlen );

            opcd e = _world->allow_attach( *_net.get_address(), rqdata, rqlen );

            if(!e)
            {
                AttachReq* par = _attbuf.add();
                par->rqid = id;
                par->addr = *_net.get_address();
                par->lag = mseclag;
                ::memcpy( par->rqdata, rqdata, rqlen );

                LOG(0) {
                    *TLOG << "ATTACH REQUEST received from " << par->addr << ", lag: " << mseclag
                    << ", clock correlation: " << int(stime-atime-mseclag) << "ms at " << timefmt() << BINSTREAM_FLUSH;
                }
            }
            else {
                LOG(0) {
                    *TLOG << "ATTACH REQUEST dismissed, from: " << *_net.get_address() << " at " << timefmt() << ", reason: "
                        << opcd_formatter(e) << BINSTREAM_FLUSH;
                }
            }

            return 1;
        }
        else if( ty == RSP_PACKET::DIFF )
        {
            ushort slot;
            _net >> slot;

            return _world->accept_client_data( _net, slot, id );
        }
        else if( ty == RSP_PACKET::DISCONNECT )
        {
            ushort slot;
            _net >> slot;

            _world->accept_client_disconnect( _net, slot, id );
            return 1;
        }

        return 0;
    }


protected:
    void sync_timer()
    {
        _Timer.reset();

        //if we are on a system that cannot sleep 1ms, try to align the timer
        // to the slice start
        for( uint i=0; i<4; ++i )
        {
            uint t1 = _Timer.time();
            sysMilliSecondSleep(1);
            uint t2 = _Timer.time();

            //linux should wait 20ms if it's at the beginning of slice
            if( t2-t1 > 19 )
            {
                _Timer.reset();
                LOG(0) { *TLOG << "timer reset, " << (t2-t1) << BINSTREAM_FLUSH; }
                break;
            }
        }

        uint t1 = _Timer.time();
        sysMilliSecondSleep(1);
        uint t2 = _Timer.time();
        LOG(0) { *TLOG << "sleep time: " << (t2-t1) << "ms, time now: " << timefmt() << BINSTREAM_FLUSH; }
    }


private:
    netstreamudp _net;

    uint _frame_usec;                       ///< milliseconds per frame

    enum {
        N_FRAMES_SYNC               = 20,   ///< frames to wait for sync message (no sleep)
    };

    //dynarray< local<rsp_s_room_controller> > _rooms;
    local<rsp_world_controller> _world;

    uint _prev_stateid;                     ///< stateid sent last

    dynarray<AttachReq> _attbuf;            ///< attach request queue
    uchar _nframesync;
    bool _bnewframe;                        ///< set if this is a new frame, and no pending msgs

};

#endif //__RSP_S_NET__HEADER_F
